<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN" lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>

<title>自动加载和重新加载常量 — Ruby on Rails Guides</title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print" />

<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shCore.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" />

<link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" />

<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
</head>
<body class="guide">
  <div id="topNav">
    <div class="wrapper">
      <strong class="more-info-label">更多内容 <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
      <span class="red-button more-info-button">
        更多内容
      </span>
      <ul class="more-info-links s-hidden">
        <li class="more-info"><a href="http://weblog.rubyonrails.org/">博客</a></li>
        <li class="more-info"><a href="http://guides.rubyonrails.org/">指南</a></li>
        <li class="more-info"><a href="http://api.rubyonrails.org/">API</a></li>
        <li class="more-info"><a href="http://stackoverflow.com/questions/tagged/ruby-on-rails">提问</a></li>
        <li class="more-info"><a href="https://github.com/rails/rails">到 GitHub 贡献</a></li>
        <li class="more-info"><a href="https://ruby-china.org/">Ruby China 社区</a></li>
      </ul>
    </div>
  </div>
  <div id="header">
    <div class="wrapper clearfix">
      <h1><a href="index.html" title="返回首页">Rails 指南</a></h1>
      <ul class="nav">
        <li><a class="nav-item" href="index.html">首页</a></li>
        <li class="guides-index guides-index-large">
          <a href="index.html" id="guidesMenu" class="guides-index-item nav-item">指南索引</a>
          <div id="guides" class="clearfix" style="display: none;">
            <hr />
              <dl class="L">
                <dt>新手入门</dt>
                <dd><a href="getting_started.html">Rails 入门</a></dd>
                <dt>模型</dt>
                <dd><a href="active_record_basics.html">Active Record 基础</a></dd>
                <dd><a href="active_record_migrations.html">Active Record 迁移</a></dd>
                <dd><a href="active_record_validations.html">Active Record 数据验证</a></dd>
                <dd><a href="active_record_callbacks.html">Active Record 回调</a></dd>
                <dd><a href="association_basics.html">Active Record 关联</a></dd>
                <dd><a href="active_record_querying.html">Active Record 查询接口</a></dd>
                <dt>视图</dt>
                <dd><a href="layouts_and_rendering.html">Rails 布局和视图渲染</a></dd>
                <dd><a href="form_helpers.html">Action View 表单辅助方法</a></dd>
                <dt>控制器</dt>
                <dd><a href="action_controller_overview.html">Action Controller 概览</a></dd>
                <dd><a href="routing.html">Rails 路由全解</a></dd>
              </dl>
              <dl class="R">
                <dt>深入探索</dt>
                <dd><a href="active_support_core_extensions.html">Active Support 核心扩展</a></dd>
                <dd><a href="i18n.html">Rails 国际化 API</a></dd>
                <dd><a href="action_mailer_basics.html">Action Mailer 基础</a></dd>
                <dd><a href="active_job_basics.html">Active Job 基础</a></dd>
                <dd><a href="testing.html">Rails 应用测试指南</a></dd>
                <dd><a href="security.html">Ruby on Rails 安全指南</a></dd>
                <dd><a href="debugging_rails_applications.html">调试 Rails 应用</a></dd>
                <dd><a href="configuring.html">配置 Rails 应用</a></dd>
                <dd><a href="command_line.html">Rails 命令行</a></dd>
                <dd><a href="asset_pipeline.html">Asset Pipeline</a></dd>
                <dd><a href="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</a></dd>
                <dd><a href="autoloading_and_reloading_constants.html">自动加载和重新加载常量</a></dd>
                <dd><a href="caching_with_rails.html">Rails 缓存概览</a></dd>
                <dd><a href="api_app.html">使用 Rails 开发只提供 API 的应用</a></dd>
                <dd><a href="action_cable_overview.html">Action Cable 概览</a></dd>
                <dt>扩展 Rails</dt>
                <dd><a href="rails_on_rack.html">Rails on Rack</a></dd>
                <dd><a href="generators.html">创建及定制 Rails 生成器和模板</a></dd>
                <dt>为 Ruby on Rails 做贡献</dt>
                <dd><a href="contributing_to_ruby_on_rails.html">为 Ruby on Rails 做贡献</a></dd>
                <dd><a href="api_documentation_guidelines.html">API 文档指导方针</a></dd>
                <dd><a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南指导方针</a></dd>
                <dt>维护方针</dt>
                <dd><a href="maintenance_policy.html">Ruby on Rails 的维护方针</a></dd>
                <dt>发布记</dt>
                <dd><a href="upgrading_ruby_on_rails.html">Ruby on Rails 升级指南</a></dd>
                <dd><a href="5_0_release_notes.html">Ruby on Rails 5.0 发布记</a></dd>
                <dd><a href="4_2_release_notes.html">Ruby on Rails 4.2 发布记</a></dd>
                <dd><a href="4_1_release_notes.html">Ruby on Rails 4.1 发布记</a></dd>
                <dd><a href="4_0_release_notes.html">Ruby on Rails 4.0 发布记</a></dd>
                <dd><a href="3_2_release_notes.html">Ruby on Rails 3.2 发布记</a></dd>
                <dd><a href="3_1_release_notes.html">Ruby on Rails 3.1 发布记</a></dd>
                <dd><a href="3_0_release_notes.html">Ruby on Rails 3.0 发布记</a></dd>
                <dd><a href="2_3_release_notes.html">Ruby on Rails 2.3 发布记</a></dd>
                <dd><a href="2_2_release_notes.html">Ruby on Rails 2.2 发布记</a></dd>
              </dl>
          </div>
        </li>
        <li><a class="nav-item" href="contributing_to_ruby_on_rails.html">贡献</a></li>
        <li><a class="nav-item" href="credits.html">感谢</a></li>
        <li class="guides-index guides-index-small">
          <select class="guides-index-item nav-item">
            <option value="index.html">指南索引</option>
              <optgroup label="新手入门">
                  <option value="getting_started.html">Rails 入门</option>
              </optgroup>
              <optgroup label="模型">
                  <option value="active_record_basics.html">Active Record 基础</option>
                  <option value="active_record_migrations.html">Active Record 迁移</option>
                  <option value="active_record_validations.html">Active Record 数据验证</option>
                  <option value="active_record_callbacks.html">Active Record 回调</option>
                  <option value="association_basics.html">Active Record 关联</option>
                  <option value="active_record_querying.html">Active Record 查询接口</option>
              </optgroup>
              <optgroup label="视图">
                  <option value="layouts_and_rendering.html">Rails 布局和视图渲染</option>
                  <option value="form_helpers.html">Action View 表单辅助方法</option>
              </optgroup>
              <optgroup label="控制器">
                  <option value="action_controller_overview.html">Action Controller 概览</option>
                  <option value="routing.html">Rails 路由全解</option>
              </optgroup>
              <optgroup label="深入探索">
                  <option value="active_support_core_extensions.html">Active Support 核心扩展</option>
                  <option value="i18n.html">Rails 国际化 API</option>
                  <option value="action_mailer_basics.html">Action Mailer 基础</option>
                  <option value="active_job_basics.html">Active Job 基础</option>
                  <option value="testing.html">Rails 应用测试指南</option>
                  <option value="security.html">Ruby on Rails 安全指南</option>
                  <option value="debugging_rails_applications.html">调试 Rails 应用</option>
                  <option value="configuring.html">配置 Rails 应用</option>
                  <option value="command_line.html">Rails 命令行</option>
                  <option value="asset_pipeline.html">Asset Pipeline</option>
                  <option value="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</option>
                  <option value="autoloading_and_reloading_constants.html">自动加载和重新加载常量</option>
                  <option value="caching_with_rails.html">Rails 缓存概览</option>
                  <option value="api_app.html">使用 Rails 开发只提供 API 的应用</option>
                  <option value="action_cable_overview.html">Action Cable 概览</option>
              </optgroup>
              <optgroup label="扩展 Rails">
                  <option value="rails_on_rack.html">Rails on Rack</option>
                  <option value="generators.html">创建及定制 Rails 生成器和模板</option>
              </optgroup>
              <optgroup label="为 Ruby on Rails 做贡献">
                  <option value="contributing_to_ruby_on_rails.html">为 Ruby on Rails 做贡献</option>
                  <option value="api_documentation_guidelines.html">API 文档指导方针</option>
                  <option value="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南指导方针</option>
              </optgroup>
              <optgroup label="维护方针">
                  <option value="maintenance_policy.html">Ruby on Rails 的维护方针</option>
              </optgroup>
              <optgroup label="发布记">
                  <option value="upgrading_ruby_on_rails.html">Ruby on Rails 升级指南</option>
                  <option value="5_0_release_notes.html">Ruby on Rails 5.0 发布记</option>
                  <option value="4_2_release_notes.html">Ruby on Rails 4.2 发布记</option>
                  <option value="4_1_release_notes.html">Ruby on Rails 4.1 发布记</option>
                  <option value="4_0_release_notes.html">Ruby on Rails 4.0 发布记</option>
                  <option value="3_2_release_notes.html">Ruby on Rails 3.2 发布记</option>
                  <option value="3_1_release_notes.html">Ruby on Rails 3.1 发布记</option>
                  <option value="3_0_release_notes.html">Ruby on Rails 3.0 发布记</option>
                  <option value="2_3_release_notes.html">Ruby on Rails 2.3 发布记</option>
                  <option value="2_2_release_notes.html">Ruby on Rails 2.2 发布记</option>
              </optgroup>
          </select>
        </li>
      </ul>
    </div>
  </div>
  <hr class="hide" />

  <div id="feature">
    <div class="wrapper">
      <h2>自动加载和重新加载常量</h2><p>本文说明常量自动加载和重新加载机制。</p><p>读完本文后，您将学到：</p>
<ul>
<li>  Ruby 常量的关键知识；</li>
<li>  <code>autoload_paths</code> 是什么；</li>
<li>  常量是如何自动加载的；</li>
<li>  <code>require_dependency</code> 是什么；</li>
<li>  常量是如何重新加载的；</li>
<li>  自动加载常见问题的解决方案。</li>
</ul>


              <div id="subCol">
          <h3 class="chapter"><img src="images/chapters_icon.gif" alt="" />目录</h3>
          <ol class="chapters">
<li><a href="#introduction">简介</a></li>
<li>
<a href="#constants-refresher">常量刷新程序</a>

<ul>
<li><a href="#nesting">嵌套</a></li>
<li><a href="#class-and-module-definitions-are-constant-assignments">定义类和模块是为常量赋值</a></li>
<li><a href="#constants-are-stored-in-modules">常量存储在模块中</a></li>
<li><a href="#resolution-algorithms">解析算法</a></li>
</ul>
</li>
<li>
<a href="#vocabulary">词汇表</a>

<ul>
<li><a href="#parent-namespaces">父级命名空间</a></li>
<li><a href="#loading-mechanism">加载机制</a></li>
</ul>
</li>
<li><a href="#autoloading-availability">自动加载可用性</a></li>
<li><a href="#autoload-paths"><code>autoload_paths</code></a></li>
<li>
<a href="#autoloading-algorithms">自动加载算法</a>

<ul>
<li><a href="#autoloading-algorithms-relative-references">相对引用</a></li>
<li><a href="#autoloading-algorithms-qualified-references">限定引用</a></li>
<li><a href="#automatic-modules">自动模块</a></li>
<li><a href="#generic-procedure">一般步骤</a></li>
</ul>
</li>
<li><a href="#require-dependency"><code>require_dependency</code></a></li>
<li><a href="#constant-reloading">常量重新加载</a></li>
<li><a href="#module-autoload-isn-t-involved"><code>Module#autoload</code> 不涉其中</a></li>
<li>
<a href="#common-gotchas">常见问题</a>

<ul>
<li><a href="#nesting-and-qualified-constants">嵌套和限定常量</a></li>
<li><a href="#autoloading-and-sti">自动加载和 STI</a></li>
<li><a href="#autoloading-and-require">自动加载和 <code>require</code></a></li>
<li><a href="#autoloading-and-initializers">自动加载和初始化脚本</a></li>
<li><a href="#require-dependency-and-initializers"><code>require_dependency</code> 和初始化脚本</a></li>
<li><a href="#when-constants-aren-t-missed">常量未缺失</a></li>
<li><a href="#autoloading-within-singleton-classes">单例类中的自动加载</a></li>
<li><a href="#autoloading-in-basicobject"><code>BasicObject</code> 中的自动加载</a></li>
</ul>
</li>
</ol>

        </div>

    </div>
  </div>

  <div id="container">
    <div class="wrapper">
      <div id="mainCol">
        <p><a class="anchor" id="introduction"></a></p><h3 id="introduction">1 简介</h3><p>编写 Ruby on Rails 应用时，代码会预加载。</p><p>在常规的 Ruby 程序中，类需要加载依赖：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
require 'application_controller'
require 'post'

class PostsController &lt; ApplicationController
  def index
    @posts = Post.all
  end
end

</pre>
</div>
<p>Ruby 程序员的直觉立即就能发现这样做有冗余：如果类定义所在的文件与类名一致，难道不能通过某种方式自动加载吗？我们无需扫描文件寻找依赖，这样不可靠。</p><p>而且，<code>Kernel#require</code> 只加载文件一次，如果修改后无需重启服务器，那么开发的过程就更为平顺。如果能在开发环境中使用 <code>Kernel#load</code>，而在生产环境使用 <code>Kernel#require</code>，那该多好。</p><p>其实，Ruby on Rails 就有这样的功能，我们刚才已经用到了：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class PostsController &lt; ApplicationController
  def index
    @posts = Post.all
  end
end

</pre>
</div>
<p>本文说明这一机制的运作原理。</p><p><a class="anchor" id="constants-refresher"></a></p><h3 id="constants-refresher">2 常量刷新程序</h3><p>在多数编程语言中，常量不是那么重要，但在 Ruby 中却是一个内容丰富的话题。</p><p>本文不会详解 Ruby 常量，但是会重点说明关键的概念。掌握以下几小节的内容对理解常量自动加载和重新加载有所帮助。</p><p><a class="anchor" id="nesting"></a></p><h4 id="nesting">2.1 嵌套</h4><p>类和模块定义可以嵌套，从而创建命名空间：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module XML
  class SAXParser
    # (1)
  end
end

</pre>
</div>
<p>类和模块的嵌套由内向外展开。嵌套可以通过 <code>Module.nesting</code> 方法审查。例如，在上述示例中，(1) 处的嵌套是</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
[XML::SAXParser, XML]

</pre>
</div>
<p>注意，组成嵌套的是类和模块“对象”，而不是访问它们的常量，与它们的名称也没有关系。</p><p>例如，对下面的定义来说</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class XML::SAXParser
  # (2)
end

</pre>
</div>
<p>虽然作用跟前一个示例类似，但是 (2) 处的嵌套是</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
[XML::SAXParser]

</pre>
</div>
<p>不含“XML”。</p><p>从这个示例可以看出，嵌套中的类或模块的名称与所在的命名空间没有必然联系。</p><p>事实上，二者毫无关系。比如说：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module X
  module Y
  end
end

module A
  module B
  end
end

module X::Y
  module A::B
    # (3)
  end
end

</pre>
</div>
<p>(3) 处的嵌套包含两个模块对象：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
[A::B, X::Y]

</pre>
</div>
<p>可以看出，嵌套的最后不是“A”，甚至不含“A”，但是包含 <code>X::Y</code>，而且它与 <code>A::B</code> 无关。</p><p>嵌套是解释器维护的一个内部堆栈，根据下述规则修改：</p>
<ul>
<li>  执行 <code>class</code> 关键字后面的定义体时，类对象入栈；执行完毕后出栈。</li>
<li>  执行 <code>module</code> 关键字后面的定义体时，模块对象入栈；执行完毕后出栈。</li>
<li>  执行 <code>class &lt;&lt; object</code> 打开的单例类时，类对象入栈；执行完毕后出栈。</li>
<li>  调用 <code>instance_eval</code> 时如果传入字符串参数，接收者的单例类入栈求值的代码所在的嵌套层次。调用 <code>class_eval</code> 或 <code>module_eval</code> 时如果传入字符串参数，接收者入栈求值的代码所在的嵌套层次.</li>
<li>  顶层代码中由 <code>Kernel#load</code> 解释嵌套是空的，除非调用 <code>load</code> 时把第二个参数设为真值；如果是这样，Ruby 会创建一个匿名模块，将其入栈。</li>
</ul>
<p>注意，块不会修改嵌套堆栈。尤其要注意的是，传给 <code>Class.new</code> 和 <code>Module.new</code> 的块不会导致定义的类或模块入栈嵌套堆栈。由此可见，以不同的方式定义类和模块，达到的效果是有区别的。</p><p><a class="anchor" id="class-and-module-definitions-are-constant-assignments"></a></p><h4 id="class-and-module-definitions-are-constant-assignments">2.2 定义类和模块是为常量赋值</h4><p>假设下面的代码片段是定义一个类（而不是打开类）：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class C
end

</pre>
</div>
<p>Ruby 在 <code>Object</code> 中创建一个常量 <code>C</code>，并将一个类对象存储在 <code>C</code> 常量中。这个类实例的名称是“C”，一个字符串，跟常量名一样。</p><p>如下的代码：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Project &lt; ApplicationRecord
end

</pre>
</div>
<p>这段代码执行的操作等效于下述常量赋值：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Project = Class.new(ApplicationRecord)

</pre>
</div>
<p>而且有个副作用——设定类的名称：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Project.name # =&gt; "Project"

</pre>
</div>
<p>这得益于常量赋值的一条特殊规则：如果被赋值的对象是匿名类或模块，Ruby 会把对象的名称设为常量的名称。</p><div class="info"><p>自此之后常量和实例发生的事情无关紧要。例如，可以把常量删除，类对象可以赋值给其他常量，或者不再存储于常量中，等等。名称一旦设定就不会再变。</p></div><p>类似地，模块使用 <code>module</code> 关键字创建，如下所示：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module Admin
end

</pre>
</div>
<p>这段代码执行的操作等效于下述常量赋值：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Admin = Module.new

</pre>
</div>
<p>而且有个副作用——设定模块的名称：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Admin.name # =&gt; "Admin"

</pre>
</div>
<div class="warning"><p>传给 <code>Class.new</code> 或 <code>Module.new</code> 的块与 <code>class</code> 或 <code>module</code> 关键字的定义体不在完全相同的上下文中执行。但是两种方式得到的结果都是为常量赋值。</p></div><p>因此，当人们说“<code>String</code> 类”的时候，真正指的是 <code>Object</code> 常量中存储的一个类对象，它存储着常量“String”中存储的一个类对象。而 <code>String</code> 是一个普通的 Ruby 常量，与常量有关的一切，例如解析算法，在 <code>String</code> 常量上都适用。</p><p>同样地，在下述控制器中</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class PostsController &lt; ApplicationController
  def index
    @posts = Post.all
  end
end

</pre>
</div>
<p><code>Post</code> 不是调用类的句法，而是一个常规的 Ruby 常量。如果一切正常，这个常量的求值结果是一个能响应 <code>all</code> 方法的对象。</p><p>因此，我们讨论的话题才是“常量”自动加载。Rails 提供了自动加载常量的功能。</p><p><a class="anchor" id="constants-are-stored-in-modules"></a></p><h4 id="constants-are-stored-in-modules">2.3 常量存储在模块中</h4><p>按字面意义理解，常量属于模块。类和模块有常量表，你可以将其理解为哈希表。</p><p>下面通过一个示例来理解。通常我们都说“<code>String</code> 类”，这样方面，下面的阐述只是为了讲解原理。</p><p>我们来看看下述模块定义：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module Colors
  RED = '0xff0000'
end

</pre>
</div>
<p>首先，处理 <code>module</code> 关键字时，解释器会在 <code>Object</code> 常量存储的类对象的常量表中新建一个条目。这个条目把“Colors”与一个新建的模块对象关联起来。而且，解释器把那个新建的模块对象的名称设为字符串“Colors”。</p><p>随后，解释模块的定义体时，会在 <code>Colors</code> 常量中存储的模块对象的常量表中新建一个条目。那个条目把“RED”映射到字符串“0xff0000”上。</p><p>注意，<code>Colors::RED</code> 与其他类或模块对象中的 <code>RED</code> 常量完全没有关系。如果存在这样一个常量，它在相应的常量表中，是不同的条目。</p><p>在前述各段中，尤其要注意类和模块对象、常量名称，以及常量表中与之关联的值对象之间的区别。</p><p><a class="anchor" id="resolution-algorithms"></a></p><h4 id="resolution-algorithms">2.4 解析算法</h4><p><a class="anchor" id="resolution-algorithm-for-relative-constants"></a></p><h5 id="resolution-algorithm-for-relative-constants">2.4.1 相对常量的解析算法</h5><p>在代码中的特定位置，假如使用 cref 表示嵌套中的第一个元素，如果没有嵌套，则表示 <code>Object</code>。</p><p>简单来说，相对常量（relative constant）引用的解析算法如下：</p>
<ol>
<li> 如果嵌套不为空，在嵌套中按元素顺序查找常量。元素的祖先忽略不计。</li>
<li> 如果未找到，算法向上，进入 cref 的祖先链。</li>
<li> 如果未找到，而且 cref 是个模块，在 <code>Object</code> 中查找常量。</li>
<li> 如果未找到，在 cref 上调用 <code>const_missing</code> 方法。这个方法的默认行为是抛出 <code>NameError</code> 异常，不过可以覆盖。</li>
</ol>
<p>Rails 的自动加载机制没有仿照这个算法，查找的起点是要自动加载的常量名称，即 cref。详情参见 <a href="#autoloading-algorithms-relative-references">相对引用</a>。</p><p><a class="anchor" id="resolution-algorithm-for-qualified-constants"></a></p><h5 id="resolution-algorithm-for-qualified-constants">2.4.2 限定常量的解析算法</h5><p>限定常量（qualified constant）指下面这种：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Billing::Invoice

</pre>
</div>
<p><code>Billing::Invoice</code> 由两个常量组成，其中 <code>Billing</code> 是相对常量，使用前一节所属的算法解析。</p><div class="info"><p>在开头加上两个冒号可以把第一部分的相对常量变成绝对常量，例如 <code>::Billing::Invoice</code>。此时，<code>Billing</code> 作为顶层常量查找。</p></div><p>而 <code>Invoice</code> 由 <code>Billing</code> 限定，下面说明它是如何解析的。假定 parent 是限定的类或模块对象，即上例中的 <code>Billing</code>。限定常量的解析算法如下：</p>
<ol>
<li> 在 parent 及其祖先中查找常量。</li>
<li> 如果未找到，调用 parent 的 <code>const_missing</code> 方法。这个方法的默认行为是抛出 <code>NameError</code> 异常，不过可以覆盖。</li>
</ol>
<p>可以看出，这个算法比相对常量的解析算法简单。毕竟这里不涉及嵌套，而且模块也不是特殊情况，如果二者及其祖先中都找不到常量，不会再查看 <code>Object</code>。</p><p>Rails 的自动加载机制没有仿照这个算法，查找的起点是要自动加载的常量名称和 parent。详情参见 <a href="#autoloading-algorithms-qualified-references">限定引用</a>。</p><p><a class="anchor" id="vocabulary"></a></p><h3 id="vocabulary">3 词汇表</h3><p><a class="anchor" id="parent-namespaces"></a></p><h4 id="parent-namespaces">3.1 父级命名空间</h4><p>给定常量路径字符串，父级命名空间是把最右边那一部分去掉后余下的字符串。</p><p>例如，字符串“A::B::C”的父级命名空间是字符串“A::B”，“A::B”的父级命名空间是“A”，“A”的父级命名空间是“”（空）。</p><p>不过涉及类和模块的父级命名空间解释有点复杂。假设有个名为“A::B”的模块 M：</p>
<ul>
<li>  父级命名空间 “A” 在给定位置可能反应不出嵌套。</li>
<li>  某处代码可能把常量 <code>A</code> 从 <code>Object</code> 中删除了，导致常量 <code>A</code> 不存在。</li>
<li>  如果 <code>A</code> 存在，<code>A</code> 中原来有的类或模块可能不再存在。例如，把一个常量删除后再赋值另一个常量，那么存在的可能就不是同一个对象。</li>
<li>  这种情形中，重新赋值的 <code>A</code> 可能是一个名为“A”的新类或模块。</li>
<li>  在上述情况下，无法再通过 <code>A::B</code> 访问 <code>M</code>，但是模块对象本身可以继续存活于某处，而且名称依然是“A::B”。</li>
</ul>
<p>父级命名空间这个概念是自动加载算法的核心，有助于以直观的方式解释和理解算法，但是并不严谨。由于有边缘情况，本文所说的“父级命名空间”真正指的是具体的字符串来源。</p><p><a class="anchor" id="loading-mechanism"></a></p><h4 id="loading-mechanism">3.2 加载机制</h4><p>如果 <code>config.cache_classes</code> 的值是 <code>false</code>（开发环境的默认值），Rails 使用 <code>Kernel#load</code> 自动加载文件，否则使用 <code>Kernel#require</code> 自动加载文件（生产环境的默认值）。</p><p>如果启用了<a href="#constant-reloading">常量重新加载</a>，Rails 通过 <code>Kernel#load</code> 多次执行相同的文件。</p><p>本文使用的“加载”是指解释指定的文件，但是具体使用 <code>Kernel#load</code> 还是 <code>Kernel#require</code>，取决于配置。</p><p><a class="anchor" id="autoloading-availability"></a></p><h3 id="autoloading-availability">4 自动加载可用性</h3><p>只要环境允许，Rails 始终会自动加载。例如，<code>runner</code> 命令会自动加载：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails runner 'p User.column_names'
["id", "email", "created_at", "updated_at"]

</pre>
</div>
<p>控制台会自动加载，测试组件会自动加载，当然，应用也会自动加载。</p><p>默认情况下，在生产环境中，Rails 启动时会及早加载应用文件，因此开发环境中的多数自动加载行为不会发生。但是在及早加载的过程中仍然可能会触发自动加载。</p><p>例如：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class BeachHouse &lt; House
end

</pre>
</div>
<p>如果及早加载 <code>app/models/beach_house.rb</code> 文件之后，<code>House</code> 尚不可知，Rails 会自动加载它。</p><p><a class="anchor" id="autoload-paths"></a></p><h3 id="autoload-paths">5 <code>autoload_paths</code>
</h3><p>或许你已经知道，使用 <code>require</code> 引入相对文件名时，例如</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
require 'erb'

</pre>
</div>
<p>Ruby 在 <code>$LOAD_PATH</code> 中列出的目录里寻找文件。即，Ruby 迭代那些目录，检查其中有没有名为“erb.rb”“erb.so”“erb.o”或“erb.dll”的文件。如果在某个目录中找到了，解释器加载那个文件，搜索结束。否则，继续在后面的目录中寻找。如果最后没有找到，抛出 <code>LoadError</code> 异常。</p><p>后面会详述常量自动加载机制，不过整体思路是，遇到未知的常量时，如 <code>Post</code>，假如 <code>app/models</code> 目录中存在 <code>post.rb</code> 文件，Rails 会找到它，执行它，从而定义 <code>Post</code> 常量。</p><p>好吧，其实 Rails 会在一系列目录中查找 <code>post.rb</code>，有点类似于 <code>$LOAD_PATH</code>。那一系列目录叫做 <code>autoload_paths</code>，默认包含：</p>
<ul>
<li>  应用和启动时存在的引擎的 <code>app</code> 目录中的全部子目录。例如，<code>app/controllers</code>。这些子目录不一定是默认的，可以是任何自定义的目录，如 <code>app/workers</code>。<code>app</code> 目录中的全部子目录都自动纳入 <code>autoload_paths</code>。</li>
<li>  应用和引擎中名为 <code>app/*/concerns</code> 的二级目录。</li>
<li>  <code>test/mailers/previews</code> 目录。</li>
</ul>
<p>此外，这些目录可以使用 <code>config.autoload_paths</code> 配置。例如，以前 <code>lib</code> 在这一系列目录中，但是现在不在了。应用可以在 <code>config/application.rb</code> 文件中添加下述配置，将其纳入其中：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.autoload_paths &lt;&lt; "#{Rails.root}/lib"

</pre>
</div>
<p>在各个环境的配置文件中不能配置 <code>config.autoload_paths</code>。</p><p><code>autoload_paths</code> 的值可以审查。在新创建的应用中，它的值是（经过编辑）：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths'
.../app/assets
.../app/controllers
.../app/helpers
.../app/mailers
.../app/models
.../app/controllers/concerns
.../app/models/concerns
.../test/mailers/previews

</pre>
</div>
<div class="info"><p><code>autoload_paths</code> 在初始化过程中计算并缓存。目录结构发生变化时，要重启服务器。</p></div><p><a class="anchor" id="autoloading-algorithms"></a></p><h3 id="autoloading-algorithms">6 自动加载算法</h3><p><a class="anchor" id="autoloading-algorithms-relative-references"></a></p><h4 id="autoloading-algorithms-relative-references">6.1 相对引用</h4><p>相对常量引用可在多处出现，例如：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class PostsController &lt; ApplicationController
  def index
    @posts = Post.all
  end
end

</pre>
</div>
<p>这里的三个常量都是相对引用。</p><p><a class="anchor" id="constants-after-the-class-and-module-keywords"></a></p><h5 id="constants-after-the-class-and-module-keywords">6.1.1 <code>class</code> 和 <code>module</code> 关键字后面的常量</h5><p>Ruby 程序会查找 <code>class</code> 或 <code>module</code> 关键字后面的常量，因为要知道是定义类或模块，还是再次打开。</p><p>如果常量不被认为是缺失的，不会定义常量，也不会触发自动加载。</p><p>因此，在上述示例中，解释那个文件时，如果 <code>PostsController</code> 未定义，Rails 不会触发自动加载机制，而是由 Ruby 定义那个控制器。</p><p><a class="anchor" id="top-level-constants"></a></p><h5 id="top-level-constants">6.1.2 顶层常量</h5><p>相对地，如果 <code>ApplicationController</code> 是未知的，会被认为是缺失的，Rails 会尝试自动加载。</p><p>为了加载 <code>ApplicationController</code>，Rails 会迭代 <code>autoload_paths</code>。首先，检查 <code>app/assets/application_controller.rb</code> 文件是否存在，如果不存在（通常如此），再检查 <code>app/controllers/application_controller.rb</code> 是否存在。</p><p>如果那个文件定义了 <code>ApplicationController</code> 常量，那就没事，否则抛出 <code>LoadError</code> 异常：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
unable to autoload constant ApplicationController, expected
&lt;full path to application_controller.rb&gt; to define it (LoadError)

</pre>
</div>
<div class="info"><p>Rails 不要求自动加载的常量是类或模块对象。假如在 <code>app/models/max_clients.rb</code> 文件中定义了 <code>MAX_CLIENTS = 100</code>，Rails 也能自动加载 <code>MAX_CLIENTS</code>。</p></div><p><a class="anchor" id="namespaces"></a></p><h5 id="namespaces">6.1.3 命名空间</h5><p>自动加载 <code>ApplicationController</code> 时直接检查 <code>autoload_paths</code> 里的目录，因为它没有嵌套。<code>Post</code> 就不同了，那一行的嵌套是 <code>[PostsController]</code>，此时就会使用涉及命名空间的算法。</p><p>对下述代码来说：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module Admin
  class BaseController &lt; ApplicationController
    @@all_roles = Role.all
  end
end

</pre>
</div>
<p>为了自动加载 <code>Role</code>，要分别检查当前或父级命名空间中有没有定义 <code>Role</code>。因此，从概念上讲，要按顺序尝试自动加载下述常量：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
Admin::BaseController::Role
Admin::Role
Role

</pre>
</div>
<p>为此，Rails 在 <code>autoload_paths</code> 中分别查找下述文件名：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
admin/base_controller/role.rb
admin/role.rb
role.rb

</pre>
</div>
<p>此外还会查找一些其他目录，稍后说明。</p><div class="info"><p>不含扩展名的相对文件路径通过 <code>'Constant::Name'.underscore</code> 得到，其中 <code>Constant::Name</code> 是已定义的常量。</p></div><p>假设 <code>app/models/post.rb</code> 文件中定义了 <code>Post</code> 模型，下面说明 Rails 是如何自动加载 <code>PostsController</code> 中的 <code>Post</code> 常量的。</p><p>首先，在 <code>autoload_paths</code> 中查找 <code>posts_controller/post.rb</code>：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
app/assets/posts_controller/post.rb
app/controllers/posts_controller/post.rb
app/helpers/posts_controller/post.rb
...
test/mailers/previews/posts_controller/post.rb

</pre>
</div>
<p>最后并未找到，因此会寻找一个类似的目录，<a href="#automatic-modules">下一节</a>说明原因：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
app/assets/posts_controller/post
app/controllers/posts_controller/post
app/helpers/posts_controller/post
...
test/mailers/previews/posts_controller/post

</pre>
</div>
<p>如果也未找到这样一个目录，Rails 会在父级命名空间中再次查找。对 <code>Post</code> 来说，只剩下顶层命名空间了：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
app/assets/post.rb
app/controllers/post.rb
app/helpers/post.rb
app/mailers/post.rb
app/models/post.rb

</pre>
</div>
<p>这一次找到了 <code>app/models/post.rb</code> 文件。查找停止，加载那个文件。如果那个文件中定义了 <code>Post</code>，那就没问题，否则抛出 <code>LoadError</code> 异常。</p><p><a class="anchor" id="autoloading-algorithms-qualified-references"></a></p><h4 id="autoloading-algorithms-qualified-references">6.2 限定引用</h4><p>如果缺失限定常量，Rails 不会在父级命名空间中查找。但是有一点要留意：缺失常量时，Rails 不知道它是相对引用还是限定引用。</p><p>例如：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module Admin
  User
end

</pre>
</div>
<p>和</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Admin::User

</pre>
</div>
<p>如果 <code>User</code> 缺失，在上述两种情况中 Rails 只知道缺失的是“Admin”模块中一个名为“User”的常量。</p><p>如果 <code>User</code> 是顶层常量，对前者来说，Ruby 会解析，但是后者不会。一般来说，Rails 解析常量的算法与 Ruby 不同，但是此时，Rails 尝试使用下述方式处理：</p>
<blockquote>
<p>如果类或模块的父级命名空间中没有缺失的常量，Rails 假定引用的是相对常量。否则是限定常量。</p>
</blockquote>
<p>例如，如果下述代码触发自动加载</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Admin::User

</pre>
</div>
<p>那么，<code>Object</code> 中已经存在 <code>User</code> 常量。但是下述代码不会触发自动加载</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module Admin
  User
end

</pre>
</div>
<p>如若不然，Ruby 就能解析出 <code>User</code>，也就无需自动加载了。因此，Rails 假定它是限定引用，只会在 <code>admin/user.rb</code> 文件和 <code>admin/user</code> 目录中查找。</p><p>其实，只要嵌套匹配全部父级命名空间，而且彼时适用这一规则的常量已知，这种机制便能良好运行。</p><p>然而，自动加载是按需执行的。如果碰巧顶层 <code>User</code> 尚未加载，那么 Rails 就假定它是相对引用。</p><p>在实际使用中，这种命名冲突很少发生。如果发生，<code>require_dependency</code> 提供了解决方案：确保做前述引文中的试探时，在有冲突的地方定义了常量。</p><p><a class="anchor" id="automatic-modules"></a></p><h4 id="automatic-modules">6.3 自动模块</h4><p>把模块作为命名空间使用时，Rails 不要求应用为之定义一个文件，有匹配命名空间的目录就够了。</p><p>假设应用有个后台，相关的控制器存储在 <code>app/controllers/admin</code> 目录中。遇到 <code>Admin::UsersController</code> 时，如果 <code>Admin</code> 模块尚未加载，Rails 要先自动加载 <code>Admin</code> 常量。</p><p>如果 <code>autoload_paths</code> 中有个名为 <code>admin.rb</code> 的文件，Rails 会加载那个文件。如果没有这么一个文件，而且存在名为 <code>admin</code> 的目录，Rails 会创建一个空模块，自动将其赋值给 <code>Admin</code> 常量。</p><p><a class="anchor" id="generic-procedure"></a></p><h4 id="generic-procedure">6.4 一般步骤</h4><p>相对引用在 cref 中报告缺失，限定引用在 parent 中报告缺失（cref 的指代参见 <a href="#resolution-algorithm-for-relative-constants">相对常量的解析算法</a>开头，parent 的指代参见 <a href="#resolution-algorithm-for-qualified-constants">限定常量的解析算法</a>开头）。</p><p>在任意的情况下，自动加载常量 C 的步骤如下：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
if the class or module in which C is missing is Object
  let ns = ''
else
  let M = the class or module in which C is missing

  if M is anonymous
    let ns = ''
  else
    let ns = M.name
  end
end

loop do
  # 查找特定的文件
  for dir in autoload_paths
    if the file "#{dir}/#{ns.underscore}/c.rb" exists
      load/require "#{dir}/#{ns.underscore}/c.rb"

      if C is now defined
        return
      else
        raise LoadError
      end
    end
  end

  # 查找自动模块
  for dir in autoload_paths
    if the directory "#{dir}/#{ns.underscore}/c" exists
      if ns is an empty string
        let C = Module.new in Object and return
      else
        let C = Module.new in ns.constantize and return
      end
    end
  end

  if ns is empty
    # 到顶层了，还未找到常量
    raise NameError
  else
    if C exists in any of the parent namespaces
      # 以限定常量试探
      raise NameError
    else
      # 在父级命名空间中再试一次
      let ns = the parent namespace of ns and retry
    end
  end
end

</pre>
</div>
<p><a class="anchor" id="require-dependency"></a></p><h3 id="require-dependency">7 <code>require_dependency</code>
</h3><p>常量自动加载按需触发，因此使用特定常量的代码可能已经定义了常量，或者触发自动加载。具体情况取决于执行路径，二者之间可能有较大差异。</p><p>然而，有时执行到某部分代码时想确保特定常量是已知的。<code>require_dependency</code> 为此提供了一种方式。它使用目前的<a href="#loading-mechanism">加载机制</a>加载文件，而且会记录文件中定义的常量，就像是自动加载的一样，而且会按需重新加载。</p><p><code>require_dependency</code> 很少需要使用，不过 <a href="#autoloading-and-sti">自动加载和 STI</a>和 <a href="#when-constants-aren-t-missed">常量未缺失</a>有几个用例。</p><div class="warning"><p>与自动加载不同，<code>require_dependency</code> 不期望文件中定义任何特定的常量。但是利用这种行为不好，文件和常量路径应该匹配。</p></div><p><a class="anchor" id="constant-reloading"></a></p><h3 id="constant-reloading">8 常量重新加载</h3><p><code>config.cache_classes</code> 设为 <code>false</code> 时，Rails 会重新自动加载常量。</p><p>例如，在控制台会话中编辑文件之后，可以使用 <code>reload!</code> 命令重新加载代码：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
&gt; reload!

</pre>
</div>
<p>在应用运行的过程中，如果相关的逻辑有变，会重新加载代码。为此，Rails 会监控下述文件：</p>
<ul>
<li>  <code>config/routes.rb</code>
</li>
<li>  本地化文件</li>
<li>  <code>autoload_paths</code> 中的 Ruby 文件</li>
<li>  <code>db/schema.rb</code> 和 <code>db/structure.sql</code>
</li>
</ul>
<p>如果这些文件中的内容有变，有个中间件会发现，然后重新加载代码。</p><p>自动加载机制会记录自动加载的常量。重新加载机制使用 <code>Module#remove_const</code> 方法把它们从相应的类和模块中删除。这样，运行代码时那些常量就变成未知了，从而按需重新加载文件。</p><div class="info"><p>这是一个极端操作，Rails 重新加载的不只是那些有变化的代码，因为类之间的依赖极难处理。相反，Rails 重新加载一切。</p></div><p><a class="anchor" id="module-autoload-isn-t-involved"></a></p><h3 id="module-autoload-isn-t-involved">9 <code>Module#autoload</code> 不涉其中</h3><p><code>Module#autoload</code> 提供的是惰性加载常量方式，深置于 Ruby 的常量查找算法、动态常量 API，等等。这一机制相当简单。</p><p>Rails 内部在加载过程中大量采用这种方式，尽量减少工作量。但是，Rails 的常量自动加载机制不是使用 <code>Module#autoload</code> 实现的。</p><p>如果基于 <code>Module#autoload</code> 实现，可以遍历应用树，调用 <code>autoload</code> 把文件名和常规的常量名对应起来。</p><p>Rails 不采用这种实现方式有几个原因。</p><p>例如，<code>Module#autoload</code> 只能使用 <code>require</code> 加载文件，因此无法重新加载。不仅如此，它使用的是 <code>require</code> 关键字，而不是 <code>Kernel#require</code> 方法。</p><p>因此，删除文件后，它无法移除声明。如果使用 <code>Module#remove_const</code> 把常量删除了，不会触发 <code>Module#autoload</code>。此外，它不支持限定名称，因此有命名空间的文件要在遍历树时解析，这样才能调用相应的 <code>autoload</code> 方法，但是那些文件中可能有尚未配置的常量引用。</p><p>基于 <code>Module#autoload</code> 的实现很棒，但是如你所见，目前还不可能。Rails 的常量自动加载机制使用 <code>Module#const_missing</code> 实现，因此才有本文所述的独特算法。</p><p><a class="anchor" id="common-gotchas"></a></p><h3 id="common-gotchas">10 常见问题</h3><p><a class="anchor" id="nesting-and-qualified-constants"></a></p><h4 id="nesting-and-qualified-constants">10.1 嵌套和限定常量</h4><p>假如有下述代码</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module Admin
  class UsersController &lt; ApplicationController
    def index
      @users = User.all
    end
  end
end

</pre>
</div>
<p>和</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Admin::UsersController &lt; ApplicationController
  def index
    @users = User.all
  end
end

</pre>
</div>
<p>为了解析 <code>User</code>，对前者来说，Ruby 会检查 <code>Admin</code>，但是后者不会，因为它不在嵌套中（参见 <a href="#nesting">嵌套</a>和 <a href="#resolution-algorithms">解析算法</a>）。</p><p>可惜，在缺失常量的地方，Rails 自动加载机制不知道嵌套，因此行为与 Ruby 不同。具体而言，在两种情况下，<code>Admin::User</code> 都能自动加载。</p><p>尽管严格来说某些情况下 <code>class</code> 和 <code>module</code> 关键字后面的限定常量可以自动加载，但是最好使用相对常量：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module Admin
  class UsersController &lt; ApplicationController
    def index
      @users = User.all
    end
  end
end

</pre>
</div>
<p><a class="anchor" id="autoloading-and-sti"></a></p><h4 id="autoloading-and-sti">10.2 自动加载和 STI</h4><p>单表继承（Single Table Inheritance，STI）是 Active Record 的一个功能，作用是在一个数据库表中存储具有层次结构的多个模型。这种模型的 API 知道层次结构的存在，而且封装了一些常用的需求。例如，对下面的类来说：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# app/models/polygon.rb
class Polygon &lt; ApplicationRecord
end

# app/models/triangle.rb
class Triangle &lt; Polygon
end

# app/models/rectangle.rb
class Rectangle &lt; Polygon
end

</pre>
</div>
<p><code>Triangle.create</code> 在表中创建一行，表示一个三角形，而 <code>Rectangle.create</code> 创建一行，表示一个长方形。如果 <code>id</code> 是某个现有记录的 ID，<code>Polygon.find(id)</code> 返回的是正确类型的对象。</p><p>操作集合的方法也知道层次结构。例如，<code>Polygon.all</code> 返回表中的全部记录，因为所有长方形和三角形都是多边形。Active Record 负责为结果集合中的各个实例设定正确的类。</p><p>类型会按需自动加载。例如，如果 <code>Polygon.first</code> 是一个长方形，而 <code>Rectangle</code> 尚未加载，Active Record 会自动加载它，然后正确实例化记录。</p><p>目前一切顺利，但是如果在根类上执行查询，需要处理子类，这时情况就复杂了。</p><p>处理 <code>Polygon</code> 时，无需知道全部子代，因为表中的所有记录都是多边形。但是处理子类时， Active Record 需要枚举类型，找到所需的那个。下面看一个例子。</p><p><code>Rectangle.all</code> 在查询中添加一个类型约束，只加载长方形：</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle")

</pre>
</div>
<p>下面定义一个 <code>Rectangle</code> 的子类：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# app/models/square.rb
class Square &lt; Rectangle
end

</pre>
</div>
<p>现在，<code>Rectangle.all</code> 返回的结果应该既有长方形，也有正方形：</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle", "Square")

</pre>
</div>
<p>但是这里有个问题：Active Record 怎么知道存在 <code>Square</code> 类呢？</p><p>如果 <code>app/models/square.rb</code> 文件存在，而且定义了 <code>Square</code> 类，但是没有代码使用它，<code>Rectangle.all</code> 执行的查询是</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle")

</pre>
</div>
<p>这不是缺陷，查询包含了所有已知的 <code>Rectangle</code> 子代。</p><p>为了确保能正确处理，而不管代码的执行顺序，可以在定义各个中间类的文件底部手动加载子类：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# app/models/rectangle.rb
class Rectangle &lt; Polygon
end
require_dependency 'square'

</pre>
</div>
<p>每个中间类（首尾之外的类）都要这么做。根类并没有通过类型限定查询，因此无需知道所有子代。</p><p><a class="anchor" id="autoloading-and-require"></a></p><h4 id="autoloading-and-require">10.3 自动加载和 <code>require</code>
</h4><p>通过自动加载机制加载的定义常量的文件一定不能使用 <code>require</code> 引入：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
require 'user' # 千万别这么做

class UsersController &lt; ApplicationController
  ...
end

</pre>
</div>
<p>如果这么做，在开发环境中会导致两个问题：</p>
<ol>
<li> 如果在执行 <code>require</code> 之前自动加载了 <code>User</code>，<code>app/models/user.rb</code> 会再次运行，因为 <code>load</code> 不会更新 <code>$LOADED_FEATURES</code>。</li>
<li> 如果 <code>require</code> 先执行了，Rails 不会把 <code>User</code> 标记为自动加载的常量，因此 <code>app/models/user.rb</code> 文件中的改动不会重新加载。</li>
</ol>
<p>我们应该始终遵守规则，使用常量自动加载机制，一定不能混用自动加载和 <code>require</code>。底线是，如果一定要加载特定的文件，使用 <code>require_dependency</code>，这样能正确利用常量自动加载机制。不过，实际上很少需要这么做。</p><p>当然，在自动加载的文件中使用 <code>require</code> 加载第三方库没问题，Rails 会做区分，不把第三方库里的常量标记为自动加载的。</p><p><a class="anchor" id="autoloading-and-initializers"></a></p><h4 id="autoloading-and-initializers">10.4 自动加载和初始化脚本</h4><p>假设 <code>config/initializers/set_auth_service.rb</code> 文件中有下述赋值语句：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
AUTH_SERVICE = if Rails.env.production?
  RealAuthService
else
  MockedAuthService
end

</pre>
</div>
<p>这么做的目的是根据所在环境为 <code>AUTH_SERVICE</code> 赋予不同的值。在开发环境中，运行这个初始化脚本时，自动加载 <code>MockedAuthService</code>。假如我们发送了几个请求，修改了实现，然后再次运行应用，奇怪的是，改动没有生效。这是为什么呢？</p><p><a href="#constant-reloading">从前文得知</a>，Rails 会删除自动加载的常量，但是 <code>AUTH_SERVICE</code> 存储的还是原来那个类对象。原来那个常量不存在了，但是功能完全不受影响。</p><p>下述代码概述了这种情况：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class C
  def quack
    'quack!'
  end
end

X = C
Object.instance_eval { remove_const(:C) }
X.new.quack # =&gt; quack!
X.name      # =&gt; C
C           # =&gt; uninitialized constant C (NameError)

</pre>
</div>
<p>鉴于此，不建议在应用初始化过程中自动加载常量。</p><p>对上述示例来说，我们可以实现一个动态接入点：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# app/models/auth_service.rb
class AuthService
  if Rails.env.production?
    def self.instance
      RealAuthService
    end
  else
    def self.instance
      MockedAuthService
    end
  end
end

</pre>
</div>
<p>然后在应用中使用 <code>AuthService.instance</code>。这样，<code>AuthService</code> 会按需加载，而且能顺利自动加载。</p><p><a class="anchor" id="require-dependency-and-initializers"></a></p><h4 id="require-dependency-and-initializers">10.5 <code>require_dependency</code> 和初始化脚本</h4><p>前面说过，<code>require_dependency</code> 加载的文件能顺利自动加载。但是，一般来说不应该在初始化脚本中使用。</p><p>有人可能觉得在初始化脚本中调用 <a href="#require-dependency"><code>require_dependency</code></a> 能确保提前加载特定的常量，例如用于解决 <a href="#autoloading-and-sti">STI 问题</a>。</p><p>问题是，在开发环境中，如果文件系统中有相关的改动，<a href="#constant-reloading">自动加载的常量会被抹除</a>。这样就与使用初始化脚本的初衷背道而驰了。</p><p><code>require_dependency</code> 调用应该写在能自动加载的地方。</p><p><a class="anchor" id="when-constants-aren-t-missed"></a></p><h4 id="when-constants-aren-t-missed">10.6 常量未缺失</h4><p><a class="anchor" id="when-constants-aren-t-missed-relative-references"></a></p><h5 id="when-constants-aren-t-missed-relative-references">10.6.1 相对引用</h5><p>以一个飞行模拟器为例。应用中有个默认的飞行模型：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# app/models/flight_model.rb
class FlightModel
end

</pre>
</div>
<p>每架飞机都可以将其覆盖，例如：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# app/models/bell_x1/flight_model.rb
module BellX1
  class FlightModel &lt; FlightModel
  end
end

# app/models/bell_x1/aircraft.rb
module BellX1
  class Aircraft
    def initialize
      @flight_model = FlightModel.new
    end
  end
end

</pre>
</div>
<p>初始化脚本想创建一个 <code>BellX1::FlightModel</code> 对象，而且嵌套中有 <code>BellX1</code>，看起来这没什么问题。但是，如果默认飞行模型加载了，但是 Bell-X1 模型没有，解释器能解析顶层的 <code>FlightModel</code>，因此 <code>BellX1::FlightModel</code> 不会触发自动加载机制。</p><p>这种代码取决于执行路径。</p><p>这种歧义通常可以通过限定常量解决：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module BellX1
  class Plane
    def flight_model
      @flight_model ||= BellX1::FlightModel.new
    end
  end
end

</pre>
</div>
<p>此外，使用 <code>require_dependency</code> 也能解决：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
require_dependency 'bell_x1/flight_model'

module BellX1
  class Plane
    def flight_model
      @flight_model ||= FlightModel.new
    end
  end
end

</pre>
</div>
<p><a class="anchor" id="when-constants-aren-t-missed-qualified-references"></a></p><h5 id="when-constants-aren-t-missed-qualified-references">10.6.2 限定引用</h5><p>对下述代码来说</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# app/models/hotel.rb
class Hotel
end

# app/models/image.rb
class Image
end

# app/models/hotel/image.rb
class Hotel
  class Image &lt; Image
  end
end

</pre>
</div>
<p><code>Hotel::Image</code> 这个表达式有歧义，因为它取决于执行路径。</p><p><a href="#resolution-algorithm-for-qualified-constants">从前文得知</a>，Ruby 会在 <code>Hotel</code> 及其祖先中查找常量。如果加载了 <code>app/models/image.rb</code> 文件，但是没有加载 <code>app/models/hotel/image.rb</code>，Ruby 在 <code>Hotel</code> 中找不到 <code>Image</code>，而在 <code>Object</code> 中能找到：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
$ bin/rails r 'Image; p Hotel::Image' 2&gt;/dev/null
Image # 不是 Hotel::Image！

</pre>
</div>
<p>若想得到 <code>Hotel::Image</code>，要确保 <code>app/models/hotel/image.rb</code> 文件已经加载——或许是使用 <code>require_dependency</code> 加载的。</p><p>不过，在这些情况下，解释器会发出提醒：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
warning: toplevel constant Image referenced by Hotel::Image

</pre>
</div>
<p>任何限定的类都能发现这种奇怪的常量解析行为：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
2.1.5 :001 &gt; String::Array
(irb):1: warning: toplevel constant Array referenced by String::Array
 =&gt; Array

</pre>
</div>
<div class="warning"><p>为了发现这种问题，限定命名空间必须是类。<code>Object</code> 不是模块的祖先。</p></div><p><a class="anchor" id="autoloading-within-singleton-classes"></a></p><h4 id="autoloading-within-singleton-classes">10.7 单例类中的自动加载</h4><p>假如有下述类定义：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# app/models/hotel/services.rb
module Hotel
  class Services
  end
end

# app/models/hotel/geo_location.rb
module Hotel
  class GeoLocation
    class &lt;&lt; self
      Services
    end
  end
end

</pre>
</div>
<p>如果加载 <code>app/models/hotel/geo_location.rb</code> 文件时 <code>Hotel::Services</code> 是已知的，<code>Services</code> 由 Ruby 解析，因为打开 <code>Hotel::GeoLocation</code> 的单例类时，<code>Hotel</code> 在嵌套中。</p><p>但是，如果 <code>Hotel::Services</code> 是未知的，Rails 无法自动加载它，应用会抛出 <code>NameError</code> 异常。</p><p>这是因为单例类（匿名的）会触发自动加载，<a href="#generic-procedure">从前文得知</a>，在这种边缘情况下，Rails 只检查顶层命名空间。</p><p>这个问题的简单解决方案是使用限定常量：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module Hotel
  class GeoLocation
    class &lt;&lt; self
      Hotel::Services
    end
  end
end

</pre>
</div>
<p><a class="anchor" id="autoloading-in-basicobject"></a></p><h4 id="autoloading-in-basicobject">10.8 <code>BasicObject</code> 中的自动加载</h4><p><code>BasicObject</code> 的直接子代的祖先中没有 <code>Object</code>，因此无法解析顶层常量：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class C &lt; BasicObject
  String # NameError: uninitialized constant C::String
end

</pre>
</div>
<p>如果涉及自动加载，情况稍微复杂一些。对下述代码来说</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class C &lt; BasicObject
  def user
    User # 错误
  end
end

</pre>
</div>
<p>因为 Rails 会检查顶层命名空间，所以第一次调用 <code>user</code> 方法时，<code>User</code> 能自动加载。但是，如果 <code>User</code> 是已知的，尤其是第二次调用 <code>user</code> 方法时，情况就不同了：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
c = C.new
c.user # 奇怪的是能正常运行，返回 User
c.user # NameError: uninitialized constant C::User

</pre>
</div>
<p>因为此时发现父级命名空间中已经有那个常量了（参见 <a href="#autoloading-algorithms-qualified-references">限定引用</a>）。</p><p>在纯 Ruby 代码中，在 <code>BasicObject</code> 的直接子代的定义体中应该始终使用绝对常量路径：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class C &lt; BasicObject
  ::String # 正确

  def user
    ::User # 正确
  end
end

</pre>
</div>


        <h3>反馈</h3>
        <p>
          我们鼓励您帮助提高本指南的质量。
        </p>
        <p>
          如果看到如何错字或错误，请反馈给我们。
          您可以阅读我们的<a href="http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation">文档贡献</a>指南。
        </p>
        <p>
          您还可能会发现内容不完整或不是最新版本。
          请添加缺失文档到 master 分支。请先确认 <a href="http://edgeguides.rubyonrails.org">Edge Guides</a> 是否已经修复。
          关于用语约定，请查看<a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南指导</a>。
        </p>
        <p>
          无论什么原因，如果你发现了问题但无法修补它，请<a href="https://github.com/rails/rails/issues">创建 issue</a>。
        </p>
        <p>
          最后，欢迎到 <a href="http://groups.google.com/group/rubyonrails-docs">rubyonrails-docs 邮件列表</a>参与任何有关 Ruby on Rails 文档的讨论。
        </p>
        <h4>中文翻译反馈</h4>
        <p>贡献：<a href="https://github.com/ruby-china/guides">https://github.com/ruby-china/guides</a>。</p>
      </div>
    </div>
  </div>

  <hr class="hide" />
  <div id="footer">
    <div class="wrapper">
      <p>本著作采用 <a href="https://creativecommons.org/licenses/by-sa/4.0/">创作共用 署名-相同方式共享 4.0 国际</a> 授权</p>
<p>“Rails”，“Ruby on Rails”，以及 Rails Logo 为 David Heinemeier Hansson 的商标。版权所有</p>

    </div>
  </div>

  <script type="text/javascript" src="javascripts/jquery.min.js"></script>
  <script type="text/javascript" src="javascripts/responsive-tables.js"></script>
  <script type="text/javascript" src="javascripts/guides.js"></script>
  <script type="text/javascript" src="javascripts/syntaxhighlighter.js"></script>
  <script type="text/javascript">
    syntaxhighlighterConfig = {
      autoLinks: false,
    };
    $(guidesIndex.bind);
  </script>
</body>
</html>
